﻿using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using Mitsubishi.Base;

namespace Mitsubishi
{
    public class MC3E : MCBase
    {
        private Socket _socket;

        public MC3E()
        {
            _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        }

        public override void Connect(string Ip, int port, int timeout = 6000)
        {
            try
            {
                _socket.Connect(Ip, port);
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }

            _socket.ReceiveTimeout = timeout;
        }

        // 0401(字、位)
        public override byte[] Read(Area area, string address, ushort count, RequestType requestType = RequestType.WORD, Action<object> callback = null)
        {
            byte[] addr_bytes = GetAddress(address, area);

            List<byte> req_bytes = new List<byte>()
            {
                0x50,0x00,
                0x00,
                0xFF,
                0xFF,0x03,
                0x00,

                0x0C,0x00,
                0x10,0x00,

                0x01,0x04,
                (byte)requestType,0x00,

                addr_bytes[0],addr_bytes[1],addr_bytes[2],
                (byte)area,
                (byte)(count % 256),(byte)(count / 256 % 256)// 读取数量
            };
            callback?.Invoke(req_bytes);

            byte[] resp_bytes = this.SendAndReceive(req_bytes.ToArray());
            callback?.Invoke(resp_bytes);
            this.CheckResponse(resp_bytes);

            byte[] datas = resp_bytes.Skip(11).ToArray();

            if (requestType == RequestType.WORD)
            {
                List<byte> res_data = new List<byte>();
                if (new Area[] { Area.X, Area.Y, Area.M }.Contains(area))
                {
                    foreach (byte b in datas)
                    {
                        for (int i = 0; i < 8; i++)
                        {
                            byte tmp = (byte)((b & (1 << i)) > 0 ? 0x01 : 0x00);
                            res_data.Add(tmp);
                        }
                    }
                }
                else
                {
                    for (int i = 0; i < datas.Length; i += 2)
                    {
                        byte[] tmp = datas.Skip(i).Take(2).Reverse().ToArray();
                        res_data.AddRange(tmp);
                    }
                }

                return res_data.ToArray();
            }
            else if (requestType == RequestType.BIT)
            {
                byte[] res_data2 = new byte[count];
                int index = 0;
                for (int i = 0; i < count; i++)
                {
                    if (i % 2 == 0)
                    {
                        res_data2[i] = (byte)((datas[index] & 0x10) > 0 ? 0x01 : 0x00);
                    }
                    else
                    {
                        res_data2[i] = (byte)((datas[index] & 0x01) > 0 ? 0x01 : 0x00);
                        index++;
                    }
                }
                return res_data2;
            }
            return null;
        }

        // 1401(字、位)
        /// <summary>
        /// 成批写入, 位写入的话 0x01 => true,0x00 => false
        /// </summary>
        /// <param name="area"></param>
        /// <param name="address"></param>
        /// <param name="datas">传入数据 (小端)  0x01 => true,0x00 => false </param>
        /// <param name="requestType"></param>
        public override void Write(Area area, string address, byte[] datas, RequestType requestType = RequestType.WORD, Action<object> callback = null)
        {
            int count = 0;
            // 数据处理
            List<byte> data_bytes = new List<byte>();
            if (requestType == RequestType.WORD)
            {
                if (new Area[] { Area.X, Area.Y, Area.M }.Contains(area))
                {
                    // datas = [ 0x01,0x00,0x00,0x01]
                    count = (int)Math.Ceiling(datas.Length * 1.0 / 16);

                    for (int i = 0; i < datas.Length; i++)
                    {
                        if (i % 8 == 0)
                            data_bytes.Add(0x00);
                        byte current = 0x00;
                        if (datas[i] == 0x01)
                            current = (byte)(current << (i % 8));
                        data_bytes[data_bytes.Count - 1] |= current;
                    }
                }
                else
                {
                    count = datas.Length / 2;
                    for (int i = 0; i < datas.Length; i += 2)
                    {
                        data_bytes.AddRange(datas.Skip(i).Take(2));
                    }
                }
            }
            else if (requestType == RequestType.BIT)
            {
                count = datas.Length;
                for (int i = 0; i < datas.Length; i++)
                {
                    if (i % 2 == 0)
                    {
                        data_bytes.Add(0x00);
                        data_bytes[data_bytes.Count - 1] |= (byte)(datas[i] << 4);
                    }
                    else
                    {
                        data_bytes[data_bytes.Count - 1] |= datas[i];
                    }
                }
            }

            byte[] addr_bytes = GetAddress(address, area);
            List<byte> req_bytes = new List<byte>()
            {
                0x50,0x00,
                0x00,
                0xFF,
                0xFF,0x03,
                0x00,

                0x0E,0x00,      // ------------------- 7,8
                0x10,0x00,

                0x01,0x14,
                (byte)requestType,0x00,

                addr_bytes[0],addr_bytes[1],addr_bytes[2],
                (byte)area,
                (byte)(count % 256),(byte)(count / 256 % 256)// 写入数量
            };

            req_bytes.AddRange(data_bytes);
            ushort data_len = (ushort)(data_bytes.Count + 12);
            req_bytes[7] = (byte)(data_len % 256);
            req_bytes[8] = (byte)(data_len / 256 % 256);
            callback?.Invoke(req_bytes);

            byte[] resp_bytes = this.SendAndReceive(req_bytes.ToArray());
            callback?.Invoke(resp_bytes);
            this.CheckResponse(resp_bytes);
        }

        // 0403(字)
        public void Mc3E_Random_Read(DataParameter[] w_params, DataParameter[] dw_params)
        {
            List<byte> req_bytes = new List<byte>()
            {
                0x50,0x00,
                0x00,
                0xFF,
                0xFF,0x03,
                0x00,

                0x1C,0x00, // -----------  7,8
                0x10,0x00,

                0x03,0x04,
                0x00,0x00,

                (byte)w_params.Length,// 后面跟着的前三个地址，按字进行数据返回
                (byte)dw_params.Length,// 后面跟着的后两个地址，按双字进行数据返回
            };

            List<byte> data_bytes = new List<byte>();
            foreach (var item in w_params)
            {
                byte[] addr_bytes = GetAddress(item.Address, item.Area);
                data_bytes.AddRange(addr_bytes);
                data_bytes.Add((byte)item.Area);
            }

            foreach (var item in dw_params)
            {
                byte[] addr_bytes = GetAddress(item.Address, item.Area);
                data_bytes.AddRange(addr_bytes);
                data_bytes.Add((byte)item.Area);
            }

            ushort req_len = (ushort)(data_bytes.Count + 8);
            req_bytes[7] = (byte)(req_len % 256);
            req_bytes[8] = (byte)(req_len / 256 % 256);

            byte[] resp_bytes = this.SendAndReceive(req_bytes.ToArray());
            this.CheckResponse(resp_bytes);

            // 解析数据
            int offset = 11;
            foreach (var item in w_params)
            {
                byte[] item_bytes = resp_bytes.Skip(offset).Take(2).ToArray();
                if (new Area[] { Area.X, Area.Y, Area.M }.Contains(item.Area))
                {
                    foreach (byte val_item in item_bytes)
                    {
                        for (int i = 0; i < 8; i++)
                        {
                            bool flag = (val_item & (0x01 << i)) > 0;
                            byte b = (byte)(flag ? 0x01 : 0x00);
                            item.Data.Add(b);
                        }
                    }
                }
                else
                {
                    Array.Reverse(item_bytes);
                    item.Data.AddRange(item_bytes);
                }
                offset += 2;
            }

            foreach (var item in dw_params)
            {
                byte[] item_bytes = resp_bytes.Skip(offset).Take(4).ToArray();
                if (new Area[] { Area.X, Area.Y, Area.M }.Contains(item.Area))
                {
                    foreach (byte val_item in item_bytes)
                    {
                        for (int i = 0; i < 8; i++)
                        {
                            bool flag = (val_item & (0x01 << i)) > 0;
                            byte b = (byte)(flag ? 0x01 : 0x00);
                            item.Data.Add(b);
                        }
                    }
                }
                else
                {
                    Array.Reverse(item_bytes);
                    item.Data.AddRange(item_bytes);
                }
                offset += 4;
            }
        }

        // 1402(位) 
        public void Mc3E_Random_Bit_Write(DataParameter[] b_params)
        {
            List<byte> req_bytes = new List<byte>()
            {
                0x50,0x00,
                0x00,
                0xFF,
                0xFF,0x03,
                0x00,

                0x1C,0x00, // -----------  7,8
                0x10,0x00,

                0x02,0x14,
                0x01,0x00,
                (byte)b_params.Length,
            };

            List<byte> req_body = new List<byte>();
            foreach (var item in b_params)
            {
                byte[] addr_bytes = GetAddress(item.Address, item.Area);
                req_body.AddRange(addr_bytes);
                req_body.Add((byte)item.Area);
                if (item.Data.Count < 0)
                    throw new Exception("数据长度异常，请检查");
                req_body.Add(item.Data[0]);
            }

            ushort len = (ushort)(7 + req_body.Count);
            req_bytes[7] = (byte)(len % 256);
            req_bytes[8] = (byte)(len / 256 % 256);
            req_bytes.AddRange(req_body);

            byte[] resp_bytes = this.SendAndReceive(req_bytes.ToArray());
            this.CheckResponse(resp_bytes);
        }

        // 1402(字)
        /// <summary>
        /// 随机字节写入
        ///     注意：这里在使用时如果是写入X,Y,M 区时，传入的数据格式还是 0x01 => true,0x00 => false
        /// </summary>
        /// <param name="w_params"></param>
        /// <param name="dw_params"></param>
        /// <exception cref="Exception"></exception>
        public void Mc3E_Random_Word_Write(List<DataParameter> w_params, List<DataParameter> dw_params)
        {
            List<byte> req_bytes = new List<byte>()
            {
                0x50,0x00,
                0x00,
                0xFF,
                0xFF,0x03,
                0x00,

                0x1C,0x00, // -----------  7,8
                0x10,0x00,

                0x02,0x14,
                0x00,0x00,
                (byte)w_params.Count,
                (byte)dw_params.Count,
            };

            List<byte> req_body = new List<byte>();
            foreach (var item in w_params)
            {
                byte[] addr_bytes = GetAddress(item.Address, item.Area);
                req_body.AddRange(addr_bytes);
                req_body.Add((byte)item.Area);
                if (new Area[] { Area.X, Area.Y, Area.M }.Contains(item.Area))
                {
                    if (item.Data.Count != 16)
                        throw new Exception("数据长度异常，请检查");
                    byte[] item_bytes = new byte[2];
                    int idx = -1;
                    for (int i = 0; i < item.Data.Count; i++)
                    {
                        if (i % 8 == 0)
                            item_bytes[++idx] = 0x00;
                        byte tmp = (byte)(item.Data[i] << (i % 8));
                        item_bytes[idx] |= tmp;
                    }
                    req_body.AddRange(item_bytes);
                }
                else
                {
                    if (item.Data.Count != 2)
                        throw new Exception("数据长度异常，请检查");
                    req_body.AddRange(item.Data);
                }
            }

            foreach (var item in dw_params)
            {
                byte[] addr_bytes = GetAddress(item.Address, item.Area);
                req_body.AddRange(addr_bytes);
                req_body.Add((byte)item.Area);
                if (new Area[] { Area.X, Area.Y, Area.M }.Contains(item.Area))
                {
                    if (item.Data.Count != 32)
                        throw new Exception("数据长度异常，请检查");
                    byte[] item_bytes = new byte[4];
                    int idx = -1;
                    for (int i = 0; i < item.Data.Count; i++)
                    {
                        if (i % 8 == 0)
                            item_bytes[++idx] = 0x00;
                        byte tmp = (byte)(item.Data[i] << (i % 8));
                        item_bytes[idx] |= tmp;
                    }
                    req_body.AddRange(item_bytes);
                }
                else
                {
                    if (item.Data.Count != 4)
                        throw new Exception("数据长度异常，请检查");
                    req_body.AddRange(item.Data);
                }
            }

            req_bytes.AddRange(req_body);
            ushort len = (ushort)(8 + req_body.Count);
            req_bytes[7] = (byte)(len % 256);
            req_bytes[8] = (byte)(len / 256 % 256);

            byte[] resp_bytes = this.SendAndReceive(req_bytes.ToArray());
            this.CheckResponse(resp_bytes);
        }

        // TODO：待完善
        // 多块读出 0x04 0x06   字操作
        // 多块批量读   字、位
        public void MultiBlockRead(List<DataParameter> w_address, List<DataParameter> b_address)
        {
            if(w_address == null ||  b_address == null)
            {
                throw new Exception("w_address 与 b_address不能为空");
            }

            List<byte> req_bytes = new List<byte>()
            {
                0x50,0x00,
                0x00,
                0xFF,
                0xFF,0x03,
                0x00,

                0x1C,0x00, // -----------  7,8
                0x10,0x00,

                0x06,0x04,
                0x00,0x00,
                (byte)w_address.Count,
                (byte)b_address.Count,
            };

            List<byte> req_body = new List<byte>();
            foreach (var item in w_address)
            {
                byte[] addr_bytes = GetAddress(item.Address, item.Area);
                req_body.AddRange(addr_bytes);
                req_body.Add((byte)item.Area);
                req_body.Add((byte)(item.Count % 256));
                req_body.Add((byte)(item.Count / 256 % 256));
            }


            foreach (var item in b_address)
            {
                if (!new Area[] { Area.X, Area.Y, Area.M }.Contains(item.Area))
                {
                    throw new Exception("位地址有误");
                }
                byte[] addr_bytes = GetAddress(item.Address, item.Area);
                req_body.AddRange(addr_bytes);
                req_body.Add((byte)item.Area);
                req_body.Add((byte)(item.Count % 256));
                req_body.Add((byte)(item.Count / 256 % 256));
            }

            req_bytes.AddRange(req_body);
            ushort len = (ushort)(8 + req_body.Count);
            req_bytes[7] = (byte)(len % 256);
            req_bytes[8] = (byte)(len / 256 % 256);

            byte[] resp_bytes = this.SendAndReceive(req_bytes.ToArray());
            this.CheckResponse(resp_bytes);

            int offset = 11;
            foreach (var item in w_address)
            {
                int count = item.Count * 2;
                byte[] arr_bytes = resp_bytes.Skip(offset).Take(count).ToArray();
                if (new Area[] { Area.X, Area.Y, Area.M }.Contains(item.Area))
                {
                    foreach (byte b in arr_bytes)
                    {
                        for (int i = 0; i < 8; i++)
                        {
                            byte tmp = (byte)((b & (1 << i)) > 0 ? 0x01 : 0x00);
                            item.Data.Add(tmp);
                        }
                    }
                }
                else
                {
                    for (int i = 0; i < arr_bytes.Length; i += 2)
                    {
                        item.Data.Add(arr_bytes[i + 1]);
                        item.Data.Add(arr_bytes[i]);
                    }
                }
                offset += count;
            }


            foreach (var item in b_address)
            {
                int count = (byte)Math.Ceiling(item.Count * 1.0 / 2);
                byte[] arr_bytes = resp_bytes.Skip(offset).Take(count).ToArray();
                int idx = 0;
                for (int i = 0; i < item.Count; i++)
                {
                    if (i % 2 == 0)
                    {
                        byte tmp = (byte)((item.Data[idx] & 0x10) > 0 ? 0x01 : 0x00);
                        item.Data.Add(tmp);
                    }
                    else
                    {
                        byte tmp = (byte)((item.Data[idx] & 0x01) > 0 ? 0x01 : 0x00);
                        item.Data.Add(tmp);
                        idx++;
                    }
                }
                offset += count;
            }
        }

        // TODO:
        // 多块写入 0x14 0x06   字操作
        // 多块批量读写   字、位
        public void MultiBlockWrite(List<DataParameter> w_address, List<DataParameter> b_address)
        {
            if (w_address == null || b_address == null)
            {
                throw new Exception("w_address 与 b_address不能为空");
            }
            List<byte> req_bytes = new List<byte>()
            {
                0x50,0x00,
                0x00,
                0xFF,
                0xFF,0x03,
                0x00,

                0x1C,0x00, // -----------  7,8
                0x10,0x00,

                0x06,0x14,
                0x00,0x00,
                (byte)w_address.Count,
                (byte)b_address.Count,
            };

            List<byte> req_body = new List<byte>();

            foreach (var item in w_address)
            {
                byte[] addr_bytes = GetAddress(item.Address, item.Area);
                req_body.AddRange(addr_bytes);
                req_body.Add((byte)item.Area);
                req_body.Add((byte)(item.Count % 256));
                req_body.Add((byte)(item.Count / 256 % 256));
            }

            foreach (var item in b_address)
            {
                if (!new Area[] { Area.X, Area.Y, Area.M }.Contains(item.Area))
                {
                    throw new Exception("位地址有误");
                }
                byte[] addr_bytes = GetAddress(item.Address, item.Area);
                req_body.AddRange(addr_bytes);
                req_body.Add((byte)item.Area);
                req_body.Add((byte)(item.Count % 256));
                req_body.Add((byte)(item.Count / 256 % 256));
            }

            req_bytes.AddRange(req_body);
            ushort len = (ushort)(8 + req_body.Count);
            req_bytes[7] = (byte)(len % 256);
            req_bytes[8] = (byte)(len / 256 % 256);

            byte[] resp_bytes = this.SendAndReceive(req_bytes.ToArray());
            this.CheckResponse(resp_bytes);
        }

        // 1001
        public void PlcRun(ExecuteType et = ExecuteType.Normal, CleanMode cm = CleanMode.Normal)
        {
            byte[] bytes = new byte[] {
                // 头部
                0x50,0x00,
                0x00,
                0xFF,
                0xFF,0x03,
                0x00,

                0x0A,0x00,// 长度需要调整
                0x10,0x00,

                0x01,0x10,
                0x00,0x00,
                (byte)et,0x00,// 是否强制执行:0x01不强制执行   0x03强制执行
                (byte)cm,// 清除模式 :00 不清空；01 清空锁存以外；02 清空全部
                0x00// 默认值
            };
            byte[] resp = this.SendAndReceive(bytes);

            this.CheckResponse(resp);
        }

        // 1002
        public void PlcStop(ExecuteType et = ExecuteType.Normal)
        {
            byte[] bytes = new byte[] {
                // 头部
                0x50,0x00,
                0x00,
                0xFF,
                0xFF,0x03,
                0x00,

                0x08,0x00,// 长度需要调整
                0x10,0x00,

                0x02,0x10,
                0x00,0x00,
                (byte)et,0x00,// 是否强制执行:0x01不强制执行   0x03强制执行
            };

            byte[] resp = this.SendAndReceive(bytes);

            this.CheckResponse(resp);
        }

        public override byte[] SendAndReceive(byte[] req_bytes)
        {
            List<byte> resp_bytes = new List<byte>();

            _socket.Send(req_bytes);
            byte[] resp_head = new byte[9];
            int count = _socket.Receive(resp_head, 0, 9, SocketFlags.None);
            if (count == 0)
                throw new Exception("请求结果异常");
            int data_len = resp_head[7] + resp_head[8] * 256;
            byte[] resp_body = new byte[data_len];
            _socket.Receive(resp_body, 0, data_len, SocketFlags.None);

            resp_bytes.AddRange(resp_head);
            resp_bytes.AddRange(resp_body);
            return resp_bytes.ToArray();
        }

        public override void CheckResponse(byte[] resp_bytes)
        {
            string status_code = string.Join("", resp_bytes.Skip(9).Take(2).Select(b => b.ToString("X2")));
            if (!"0000".Equals(status_code))
            {
                if (McStatus.Errors.ContainsKey(status_code))
                    throw new Exception(McStatus.Errors[status_code]);
                else
                    throw new Exception("未知错误");

            }
        }

        public override void Disconnect()
        {
            if (_socket == null || !_socket.Connected) return;
            _socket.Disconnect(true);
            _socket.Close();
            _socket.Dispose();
        }
    }
}
